#pragma once
#include <cmath>
#include <iostream>
#include "vutility.h"
#include "vfwd.h"
#include "blitzdef.h"
#include "blitzutility.h"

namespace geometry {
class Vector2D
{
	friend class Point2D;

public:
	Vector2D(void) : vx(0), vy(0) {}
	Vector2D(double vxx, double vyy) : vx(vxx), vy(vyy) {}
	Vector2D(const Vector2D& vt) : vx(vt.vx), vy(vt.vy) {}
	Vector2D(const Point2D& start, const Point2D& end);

	Vector2D& operator=(const Vector2D& rvt) {
		// Handle self-assignment:
		if(this==&rvt) return *this;
		vx = rvt.vx;
		vy = rvt.vy;
		return *this;
	}

	~Vector2D(void) { }

public:
	template<typename T> T GetVX() const { return vx; }
	template<typename T> T GetVY() const { return vy; }
	void SetVX(double vxx) { vx=vxx; }
	void SetVY(double vyy) { vy=vyy; }
	void SetVXY(double vxx, double vyy) { vx=vxx; vy=vyy; }

public:
	template<typename T> T Length() const { return sqrt(vx*vx+vy*vy); }
	template<typename T> T Length2() const { return vx*vx+vy*vy; }

	template<typename T> T Angle() const
  {
    return fmod(DEGREE(atan2(vy, vx))+360, 360.0);
  }

	const Vector2D Normalize() const { return *this/Length<double>(); }

	const Vector2D Rotate(double alpha) const
  { 
    double sv = sin(RADIAN(alpha));
    double cv = cos(RADIAN(alpha));
    return Vector2D(vx*cv-vy*sv, vx*sv+vy*cv);
  }

	const Vector2D Rotate(double sv, double cv) const
  {
    return Vector2D(vx*cv-vy*sv, vx*sv+vy*cv);
  }

public:
	// Conditional operators return true/false
	bool operator==(const Vector2D& rvt) const { return vx==rvt.vx && vy==rvt.vy; }
	bool operator!=(const Vector2D& rvt) const { return !(*this==rvt); }

	// -vt
	// Unary negative operator
	// The resulting new vector has components (-vx, -vy)
	friend const Vector2D operator-(const Vector2D& vt)
  {
    return Vector2D(-vt.vx, -vt.vy);
  }

	// ~vt
	// Unary 2D perp operator
	// The resulting new vector has components (-vy, vx), which is leftward normal to v
	friend const Vector2D operator~(const Vector2D& vt)
  { 
    return Vector2D(-vt.vy, vt.vx);
  }

	// lvt+rvt
	// Sum of two vectors
	// The resulting new vector has components (lvx+rvx, lvy+rvy)
	friend const Vector2D operator+(const Vector2D& lvt, const Vector2D& rvt)
  { 
    return Vector2D(lvt.vx+rvt.vx, lvt.vy+rvt.vy);
  }

	// lvt-rvt
	// Difference of two vectors
	// The resulting new vector has components (lvx-rvx, lvy-rvy)
	friend const Vector2D operator-(const Vector2D& lvt, const Vector2D& rvt) 
  { 
    return Vector2D(lvt.vx-rvt.vx, lvt.vy-rvt.vy); 
  }

	// vt*sv, sv*vt
	// Scalar multiplication of vector vt by scalar sv
	// The resulting new vector has components (sv*vtx, sv*vty)
	friend const Vector2D operator*(const Vector2D& lvt, const double rsv) 
  { 
    return Vector2D(lvt.vx*rsv, lvt.vy*rsv); 
  }

	friend const Vector2D operator*(const double lsv, const Vector2D& rvt) 
  { 
    return rvt*lsv; 
  }

	// lvt/rsv
	// Scalar division of vector lvt by scalar rsv
	// The resulting new vector has components (lvx/rsv, lvy/rsv)
	friend const Vector2D operator/(const Vector2D& lvt, const double rsv) 
  { 
    return Vector2D(lvt.vx/rsv, lvt.vy/rsv); 
  }

	// lvt*rvt
	// Dot product of two vectors
	// The resulting scalar value = lvx*rvx + lvy*rvy
	friend double operator*(const Vector2D& lvt, const Vector2D& rvt) 
  { 
    return lvt.vx*rvt.vx+lvt.vy*rvt.vy; 
  }

	// lvt|rvt
	// Perp product of two vectors
	// The resulting scalar value = lvx*rvy - lvy*rvx
	friend double operator|(const Vector2D& lvt, const Vector2D& rvt) 
  { 
    return lvt.vx*rvt.vy-lvt.vy*rvt.vx; 
  }

public:
	// lvt += rvt
	// Increment vector lvt by vector rvt
	// The vector lvt has its components changed to (lvx+rvx, lvy+rvy)
	Vector2D& operator+=(const Vector2D& rvt) 
  { 
    vx+=rvt.vx; 
    vy+=rvt.vy; 
    return *this; 
  }

	// lvt -= rvt
	// Decrement vector lvt by vector rvt
	// The vector lvt has its components changed to (lvx-rvx, lvy-rvy)
	Vector2D& operator-=(const Vector2D& rvt) 
  { 
    vx-=rvt.vx; 
    vy-=rvt.vy; 
    return *this; 
  }

	// lvt *= rsv
	// Scalar multiplication of vector lvt by scalar rsv
	// The vector lvt has its components changed to (lvx*rsv, lvy*rsv)
	Vector2D& operator*=(const double rsv) 
  { 
    vx*=rsv; 
    vy*=rsv; 
    return *this; 
  }

	// lvt /= rsv
	// Scalar division of vector lvt by scalar rsv
	// The vector lvt has its components changed to (lvx/rsv, lvy/rsv)
	Vector2D& operator/=(const double rsv) 
  { 
    vx/=rsv; 
    vy/=rsv; 
    return *this; 
  }

public:
	// Query if vector vt is left of this vector
	bool IsLeft(const Vector2D& vt) const 
  { 
    return (*this|vt)>0; 
  }

private:
	double vx, vy;
	friend std::ostream& operator<<(std::ostream& os, const Vector2D& vt)
  {
		return os<<"(vx="<<vt.vx<<", "<<"vy="<<vt.vy<<")";
	}
};

class Point2D
{
	friend class Vector2D;
	friend class Polygon2D;
	friend class Line2D;
	friend class Segment2D;

public:
	// Creates a default 2D point with x=y=0
	Point2D(void) : x_(0), y_(0) { }

	// Creates a 2D point with x=xx, y=yy
	Point2D(double x, double y) : x_(x), y_(y) { }

	// Creates a 2D point from another 2D point
	Point2D(const Point2D& pt) : x_(pt.x_), y_(pt.y_)  { }

	// Assigns a 2D point from another 2D point
	Point2D& operator=(const Point2D& rpt) {
		// Handle self-assignment:
		if(this==&rpt) return *this;
		x_ = rpt.x_;
		y_ = rpt.y_;
		return *this;
	}

	~Point2D(void) { }

public:
	template<typename T> T GetX() const { return x_; }
	template<typename T> T GetY() const { return y_; }
	void set_x(double x) { x_=x; }
	void set_y(double y) { y_=y; }
	void set_x_y(double x, double y) { x_=x; y_=y; }

public:
	// Conditional operators return true/false
	bool operator==(const Point2D& rpt) const
  {
		return x_==rpt.x_ && y_==rpt.y_;
	}

	bool operator!=(const Point2D& rpt) const
  {
		return !(*this==rpt);
	}

	// Arithmetic operator overloading (No side effects)
	const Point2D operator+(const Vector2D& rvt) const 
  { 
    return Point2D(x_+rvt.vx, y_+rvt.vy); 
  }

	const Point2D operator-(const Vector2D& rvt) const
  { 
    return Point2D(x_-rvt.vx, y_-rvt.vy);
  }

	// Arithmetic operator overloading (Side effects)
	Point2D& operator+=(const Vector2D& rvt)
  { 
    x_ += rvt.vx;
    y_ += rvt.vy;

    return *this;
  }

	Point2D& operator-=(const Vector2D& rvt)
  { 
    x_ -= rvt.vx;
    y_ -= rvt.vy;
    
    return *this;
  }

	// Point Scalar Operations (convenient but often illegal) using any type of
	// scalar (int, float, or double) are not valid for points in general, unless
	// they are 'affine' as coeffs of a sum in which all the coeffs add to 1,
	// such as: the sum (a*P + b*Q) with (a+b == 1).
	// The programmer must enforce this (if they want to)
	friend const Point2D operator+(const Point2D& lpt, const Point2D& rpt)
  { 
    return Point2D(lpt.x_+rpt.x_, lpt.y_+rpt.y_); 
  }

	friend const Vector2D operator-(const Point2D& lpt, const Point2D& rpt)
  { 
    return Vector2D(lpt.x_-rpt.x_, lpt.y_-rpt.y_); 
  }

	friend const Point2D operator*(const Point2D& lpt, const double rs)
  { 
    return Point2D(lpt.x_*rs, lpt.y_*rs); 
  }

	friend const Point2D operator*(const double ls, const Point2D& rpt)
  { 
    return Point2D(rpt.x_*ls, rpt.y_*ls); 
  }

	friend const Point2D operator/(const Point2D& lpt, const double rs)
  { 
    return Point2D(lpt.x_/rs, lpt.y_/rs); 
  }

public:
	template<typename T> T Distance(const Point2D& pt) const
  {
		double dx = pt.x_ - x_;
		double dy = pt.y_ - y_;

		return sqrt(dx*dx + dy*dy);
	}

	template<typename T> T Distance2(const Point2D& pt) const
  {
		double dx = pt.x_ - x_;
		double dy = pt.y_ - y_;

		return dx*dx + dy*dy;
	}

public:
	// Test if *this is Left|On|Right of an infinite line through start and end
	// Return:	>0 for *this left of the line through start and end
	//			=0 for *this on the line
	//			<0 for *this right of the line
	double IsLeft(const Point2D& start, const Point2D& end) const
  { 
    return (end.x_-start.x_)*(y_-start.y_)-(x_-start.x_)*(end.y_-start.y_); 
  }

	// Test the orientation of a triangle with three vertex points *this, vpt1, vpt2
	// Return:	>0 for counterclockwise
	//			=0 for none (degenerate)
	//			<0 for clockwise
	double TriangleOrientation(const Point2D& vpt1, const Point2D& vpt2) const
  { 
    return IsLeft(vpt1, vpt2);
  }

	// Compute the area of a triangle with three vertex points *this, vpt1, vpt2
	double TriangleArea(const Point2D& vpt1, const Point2D& vpt2) const
  { 
    return IsLeft(vpt1, vpt2)/2;
  }

	// Rotate a 2D point by angle alpha counterclockwise
	Point2D Rotate(double alpha) const
  { 
    double sv = sin(RADIAN(alpha));
    double cv = cos(RADIAN(alpha));

    return Point2D(x_*cv-y_*sv, x_*sv+y_*cv);
  }

	Point2D Rotate(double sv, double cv) const
  {
    return Point2D(x_*cv-y_*sv, x_*sv+y_*cv);
  }

public:
  double x(void) const { return x_; }
  double y(void) const { return y_; }

private:
	double x_;
  double y_;

	friend std::ostream& operator<<(std::ostream& os, const Point2D& pt)
  {
		return os<<"(x="<<pt.x_<<", "<<"y="<<pt.y_<<")";
	}
};

// Vector2D implementation
inline Vector2D::Vector2D(const Point2D& start, const Point2D& end)
  : vx(end.x_-start.x_), vy(end.y_-start.y_) 
{
}

template<> inline float Vector2D::Length<float>() const
{ 
  return static_cast<float>(Length<double>());
}

template<> inline int Vector2D::Length<int>() const
{ 
  return vroundp<int>(Length<double>());
}

template<> inline float Vector2D::Length2<float>() const
{ 
  return static_cast<float>(Length2<double>());
}

template<> inline int Vector2D::Length2<int>() const
{ 
  return vroundp<int>(Length2<double>());
}

template<> inline float Vector2D::Angle<float>() const
{ 
  return static_cast<float>(Angle<double>());
}

template<> inline int Vector2D::Angle<int>() const
{ 
  return vroundp<int>(Angle<double>());
}

template<> inline int Vector2D::GetVX<int>() const
{ 
  return vround<int>(vx);
}

template<> inline int Vector2D::GetVY<int>() const
{ 
  return vround<int>(vy);
}

template<> inline long Vector2D::GetVX<long>() const
{ 
  return vround<long>(vx);
}

template<> inline long Vector2D::GetVY<long>() const
{ 
  return vround<long>(vy);
}

template<> inline float Vector2D::GetVX<float>() const
{ 
  return static_cast<float>(vx);
}

template<> inline float Vector2D::GetVY<float>() const
{ 
  return static_cast<float>(vy); 
}

// Point2D implementation
template<>
inline int Point2D::GetX<int>() const
{ 
  return vround<int>(x_);
}

template<>
inline int Point2D::GetY<int>() const
{ 
  return vround<int>(y_);
}

template<>
inline long Point2D::GetX<long>() const 
{ 
  return vround<long>(x_);
}

template<>
inline long Point2D::GetY<long>() const
{ 
  return vround<long>(y_);
}

template<>
inline float Point2D::GetX<float>() const
{ 
  return static_cast<float>(x_); 
}

template<>
inline float Point2D::GetY<float>() const
{ 
  return static_cast<float>(y_);
}

template<>
inline float Point2D::Distance<float>(const Point2D& pt) const
{
	return static_cast<float>(Distance<double>(pt));
}

template<>
inline int Point2D::Distance<int>(const Point2D& pt) const
{
	return vroundp<int>(Distance<double>(pt));
}

template<>
inline float Point2D::Distance2<float>(const Point2D& pt) const
{
	return static_cast<float>(Distance2<double>(pt));
}

template<>
inline int Point2D::Distance2<int>(const Point2D& pt) const
{
	return vroundp<int>(Distance2<double>(pt));
}

// Convenient function
inline Vector2D v2dNegate(const Vector2D& vt) { return -vt; }
inline Vector2D v2dPerp(const Vector2D& vt) { return ~vt; }

inline Vector2D v2dSum(const Vector2D& lvt, const Vector2D& rvt)
{ 
  return lvt+rvt; 
}

inline Vector2D v2dDiff(const Vector2D& lvt, const Vector2D& rvt)
{ 
  return lvt-rvt; 
}

inline Vector2D v2dMul(const Vector2D& lvt, const double rsv)
{ 
  return lvt*rsv; 
}

inline Vector2D v2dMul(const double lsv, const Vector2D& rvt)
{ 
  return rvt*lsv; 
}

inline Vector2D v2dDiv(const Vector2D& lvt, const double rsv)
{ 
  return lvt/rsv; 
}

inline double v2dDotProduct(const Vector2D& lvt, const Vector2D& rvt)
{ 
  return lvt*rvt; 
}

inline double v2dPerpProduct(const Vector2D& lvt, const Vector2D& rvt)
{ 
  return lvt|rvt; 
}

class Size2D {
public:
  Size2D() 
  {
    sx_ = 0;
    sy_ = 0;
  }

  Size2D(double sx, double sy)
  {
    sx_ = sx;
    sy_ = sy;
  }

  ~Size2D() {}

  bool operator<(const Size2D& size)
  {
    return (sx_<size.sx_ && sy_<size.sy_);
  }

private:
  double sx_;
  double sy_;
};

class Rect2D {
public:
  Rect2D()
  {
    top_    = 0;
    left_   = 0;
    bottom_ = 0;
    right_  = 0;
  }

  Rect2D(double top, double left, double bottom, double right)
  {
    top_    = top;
    left_   = left;
    bottom_ = bottom;
    right_  = right;
  }

  Rect2D(const Point2D& top_left, const Point2D& bottom_right)
  {
    top_    = top_left.GetY<double>();
    left_   = top_left.GetX<double>();
    bottom_ = bottom_right.GetY<double>();
    right_  = bottom_right.GetX<double>();
  }

  Rect2D(const Point2D& center, double width, double height)
  {
    top_    = center.GetY<double>() - height/2;
    left_   = center.GetX<double>() - width/2;
    bottom_ = center.GetY<double>() + height/2;
    right_  = center.GetX<double>() + width/2;
  }

  Rect2D(const Point2D& center, double size)
  {
    top_    = center.GetY<double>() - size/2;
    left_   = center.GetX<double>() - size/2;
    bottom_ = center.GetY<double>() + size/2;
    right_  = center.GetX<double>() + size/2;
  }

  Rect2D(double x, double y, double size)
  {
    top_    = y - size/2;
    left_   = x - size/2;
    bottom_ = y + size/2;
    right_  = x + size/2;
  }

  Rect2D& operator=(const Rect2D& rrect) {
    // Handle self-assignment:
    if(this==&rrect) {
      return *this;
    }

    left_   = rrect.left_;
    right_  = rrect.right_;
    top_    = rrect.top_;
    bottom_ = rrect.bottom_;

    return *this;
  }

  ~Rect2D() {}

public:
  template<typename T> T top(void) const { return top_; }
  template<> int top(void) const { return vround<int>(top_); }
  template<> long top(void) const { return vround<long>(top_); }
  template<> float top(void) const { return static_cast<float>(top_); }
  void set_top(double top) { top_ = top; }

  template<typename T> T left(void) const { return left_; }
  template<> int left(void) const { return vround<int>(left_); }
  template<> long left(void) const { return vround<long>(left_); }
  template<> float left(void) const { return static_cast<float>(left_); }
  void set_left(double left) { left_ = left; }

  template<typename T> T bottom(void) const { return bottom_; }
  template<> int bottom(void) const { return vround<int>(bottom_); }
  template<> long bottom(void) const { return vround<long>(bottom_); }
  template<> float bottom(void) const { return static_cast<float>(bottom_); }
  void set_bottom(double bottom) { bottom_ = bottom; }

  template<typename T> T right(void) const { return right_; }
  template<> int right(void) const { return vround<int>(right_); }
  template<> long right(void) const { return vround<long>(right_); }
  template<> float right(void) const { return static_cast<float>(right_); }
  void set_right(double right) { top_ = right; }

  double width(void) const { return fabs(right_-left_); }
  double height(void) const { return fabs(bottom_-top_); }
  double x(void) const { return (left_+right_)/2; }
  double y(void) const { return (top_+bottom_)/2; }

  Point2D center(void) const { return Point2D(x(), y()); }

public:
  bool PtInRect(const Point2D& point) const
  {
    return point.GetX<double>()<right_ && point.GetX<double>()>left_ &&
      point.GetY<double>()<bottom_ && point.GetY<double>()>top_;
  }

private:
  double top_;
  double left_;
  double bottom_;
  double right_;
};
}